﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Text.RegularExpressions;
using System.ComponentModel;
using WinSCP;

namespace Backup_IOS
{
    /// <summary>
    /// Interaction logic for winOperation.xaml
    /// </summary>
    public partial class winOperation : Window
    {
        public winOperation()
        {
            InitializeComponent();
        }

        string LogPath = "";
        string Operation = "";
        Dictionary<string, object> Parameters = null;
        readonly BackgroundWorker worker = new BackgroundWorker();

        /// <summary>
        /// Establish which operation to perform, does not actually perform the operation until the window is loaded
        /// </summary>
        /// <param name="Operation"></param>
        /// <param name="Parameters"></param>
        public void PerformOperation(string Operation, Dictionary<string, object> Parameters)
        {
            //Generate a random filename based on the operation to perform
            var FN = Regex.Replace(Operation, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled) + "_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss");

            LogPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), FN);
            var orgLogPath = LogPath;
            var i = 0;
            while (File.Exists(LogPath + ".log"))
            {
                LogPath = orgLogPath + "_" + i;
                i++;
            }
            LogPath += ".log";
            AddLog("Log File: " + LogPath);

            AddLog("Operation: " + Operation);
            AddLog("Started at: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss"));

            //Store the operation and parameters, actual operation will be done when window is loaded
            this.Operation = Operation;
            this.Parameters = Parameters;
        }

        /// <summary>
        /// Adds a log record to screen and to file
        /// </summary>
        /// <param name="s"></param>
        void AddLog(string s, TextBox txt=null, bool Overwrite=false)
        {
            var Log = s + Environment.NewLine;
            if (txt == null)
                txt = txt_Log;

            //Add the log to text then scroll to the end
            Dispatcher.BeginInvoke(new Action(() => { txt.Text += Log; txt.CaretIndex = txt.Text.Length - 1; txt.ScrollToEnd(); })); 

            //write the log to file
            using (var sw = new StreamWriter(LogPath, true))
            {
                sw.Write(Log);
            }
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
         
            //Run the operation as an async job
            worker.RunWorkerAsync();
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            DoOperation(this.txt_Log);
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            AddLog("Completed");
        }

        /// <summary>
        /// Performs the actual operation
        /// </summary>
        void DoOperation(TextBox txt)
        {
            var HostName = App.Settings.SSH_Host;
            var Port = 22;
            if (HostName.IndexOf(":")>0)
            {
                HostName = App.Settings.SSH_Host.Substring(0, App.Settings.SSH_Host.IndexOf(":"));
                Port = Convert.ToInt32(App.Settings.SSH_Host.Substring(App.Settings.SSH_Host.IndexOf(":") + 1));
                //MessageBox.Show(HostName + Environment.NewLine + Port);
            }

            try
            {
                // Setup session options
                SessionOptions sessionOptions = new SessionOptions
                {
                    Protocol = Protocol.Sftp,
                    HostName = HostName,
                    PortNumber = Port,
                    UserName = App.Settings.SSH_UserID,
                    Password = App.Settings.SSH_Password,
                    GiveUpSecurityAndAcceptAnySshHostKey = true
                };

                const string Path_Applications = "/private/var/mobile/Applications/";

                using (Session session = new Session())
                {
                    session.FileTransferProgress += session_FileTransferProgress;
                    // Connect
                    AddLog("Connecting to SSH Host");
                    session.Open(sessionOptions);

                    AddLog("Connection to SSH Host: SUCCESSFUL");

                    RemoteDirectoryInfo directory = session.ListDirectory(Path_Applications);

                    switch (Operation)
                    {
                        case "OneClick-Backup":
                            #region OneClick-Backup
                            if (App.Settings.Backup_AddressBook)
                                GetFiles(session, "Backing up - AddressBook", txt, "/var/mobile/Library/AddressBook/", Path.Combine(App.Settings.Local_Backup_Path, "ios-AddressBook") + @"\");

                            if (App.Settings.Backup_Calendar)
                                GetFiles(session, "Backing up - Calendar", txt, "/var/mobile/Library/Calendar/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Calendar") + @"\");

                            if (App.Settings.Backup_Documents)
                                GetFiles(session, "Backing up - Documents", txt, "/var/mobile/Documents/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Documents") + @"\");

                            if (App.Settings.Backup_Photos)
                                GetFiles(session, "Backing up - Photos", txt, "/var/mobile/Media/DCIM/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Photos") + @"\");

                            if (App.Settings.Backup_SMS)
                                GetFiles(session, "Backing up - SMS", txt, "/var/mobile/Library/SMS/", Path.Combine(App.Settings.Local_Backup_Path, "ios-SMS") + @"\");

                            if (App.Settings.Backup_Notes)
                                GetFiles(session, "Backing up - Notes", txt, "/var/mobile/Library/Notes/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Notes") + @"\");

                            AddLog("Backing up - Applications", txt);

                            var lApps = new List<string>();

                            foreach (RemoteFileInfo fileInfo in directory.Files)
                            {
                                if (fileInfo.IsDirectory)
                                {
                                    var AppName = fileInfo.Name;

                                    if (AppName == "." || AppName == "..")
                                    {
                                    }
                                    else
                                    {
                                        //this is an application
                                        //iterate thru all the files to find one ending with .app (this is the app name)
                                        var hasDocuments = false;
                                        foreach (RemoteFileInfo fi in session.ListDirectory(Path_Applications + fileInfo.Name + "/").Files)
                                        {
                                            if (fi.Name.EndsWith(".app"))
                                            {
                                                //this is the appName
                                                AppName = fi.Name;
                                            }
                                            else if (fi.Name == "Documents")
                                            {
                                                hasDocuments = true;
                                            }
                                        }
                                        lApps.Add(AppName);

                                        if (!isRetrictedAppName(AppName) && hasDocuments)
                                        {
                                            GetFiles(session, "Application: " + AppName, txt, Path_Applications + fileInfo.Name + "/Documents/", Path.Combine(App.Settings.Local_Backup_Path, AppName));
                                        }
                                    }
                                }
                            }

                            File.WriteAllLines(Path.Combine(App.Settings.Local_Backup_Path, "Applications List.txt"), lApps.OrderBy(p => p).ToArray());
                            #endregion
                            break;

                        case "OneClick-Restore":
                            #region OneClick-Restore

                            if (App.Settings.Backup_AddressBook)
                                PutFiles(session, "Restoring - AddressBook", txt, "/var/mobile/Library/AddressBook/", Path.Combine(App.Settings.Local_Backup_Path, "ios-AddressBook") + @"\*");

                            if (App.Settings.Backup_Calendar)
                                PutFiles(session, "Restoring - Calendar", txt, "/var/mobile/Library/Calendar/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Calendar") + @"\*");

                            if (App.Settings.Backup_Documents)
                                PutFiles(session, "Restoring - Documents", txt, "/var/mobile/Documents/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Documents") + @"\*");

                            if (App.Settings.Backup_Photos)
                                PutFiles(session, "Restoring - Photos", txt, "/var/mobile/Media/DCIM/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Photos") + @"\*");

                            if (App.Settings.Backup_SMS)
                                PutFiles(session, "Restoring - SMS", txt, "/var/mobile/Library/SMS/", Path.Combine(App.Settings.Local_Backup_Path, "ios-SMS") + @"\*");

                            if (App.Settings.Backup_Notes)
                                PutFiles(session, "Restoring up - Notes", txt, "/var/mobile/Library/Notes/", Path.Combine(App.Settings.Local_Backup_Path, "ios-Notes") + @"\");


                            AddLog("Restoring - Applications");

                            foreach (RemoteFileInfo fileInfo in directory.Files)
                            {
                                if (fileInfo.IsDirectory)
                                {
                                    var AppName = fileInfo.Name;

                                    if (AppName == "." || AppName == "..")
                                    {
                                    }
                                    else
                                    {
                                        //this is an application
                                        //iterate thru all the files to find one ending with .app (this is the app name)
                                        foreach (RemoteFileInfo fi in session.ListDirectory(Path_Applications + fileInfo.Name + "/").Files)
                                        {
                                            if (fi.Name.EndsWith(".app"))
                                            {
                                                //this is the appName
                                                AppName = fi.Name;
                                            }
                                        }

                                        if (!isRetrictedAppName(AppName))
                                        {
                                            var LocalPath = Path.Combine(App.Settings.Local_Backup_Path, AppName);
                                            if (Directory.Exists(LocalPath))
                                                PutFiles(session, "Application: " + AppName, txt, Path_Applications + fileInfo.Name + "/Documents/", LocalPath + @"\*");
                                        }
                                    }
                                }
                            }
                            #endregion
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                AddLog("Error: " + ex);
            }

            AddLog("Closing SSH Session");
        }

        void session_FileTransferProgress(object sender, FileTransferProgressEventArgs e)
        {
            var s = Path.GetFileName(e.FileName) + " - " + (e.FileProgress * 100).ToString("0.0") + "% @ " + (1.0*e.CPS/1024/1024).ToString("0.00") + "MB/s";
            Dispatcher.BeginInvoke(new Action(() => { txt_Progress.Text = s;})); 
        }

        /// <summary>
        /// Retrieve files from RemotePath to LocalPath via the SFTP session
        /// </summary>
        /// <param name="session"></param>
        /// <param name="Operation">Header message to put in the log</param>
        /// <param name="RemotePath"></param>
        /// <param name="LocalPath"></param>
        void GetFiles(Session session, string Operation, TextBox txt, string RemotePath, string LocalPath)
        {
            try
            {
                AddLog(Operation);
                AddLog("Remote Path: " + RemotePath);
                AddLog("Local Path: " + LocalPath);
                if (!Directory.Exists(LocalPath))
                    Directory.CreateDirectory(LocalPath);
                var r = session.GetFiles(RemotePath, LocalPath);
                LogTransfer(r, txt);
            }
            catch (Exception ex)
            {
                AddLog(ex.ToString(), txt);
            }
        }

        /// <summary>
        /// Upload files from LocalPath to RemotePath via the SFTP session
        /// </summary>
        /// <param name="session"></param>
        /// <param name="Operation">Header message to put in the log</param>
        /// <param name="RemotePath"></param>
        /// <param name="LocalPath"></param>
        void PutFiles(Session session, string Operation, TextBox txt, string RemotePath, string LocalPath)
        {
            AddLog(Operation);
            AddLog("Remote Path: " + RemotePath);

            var LocalPath2 = LocalPath;
            if (LocalPath2.EndsWith(@"\*"))
                LocalPath2 = LocalPath2.Substring(0, LocalPath2.Length - 2);

            AddLog("Local Path: " + LocalPath);
            AddLog("Local Path (Actual): " + LocalPath2);
            if (!Directory.Exists(LocalPath2))
            {
                AddLog("Local Path DOES NOT EXIST", txt);
                return;
            }

            if (Directory.GetFiles(LocalPath2).Count() < 1)
            {
                AddLog("No files to restore");
                return;
            }
            else
            {
                try
                {
                    var r = session.PutFiles(LocalPath, RemotePath);
                    LogTransfer(r, txt);

                    var cmd = "chown mobile " + RemotePath + "*";
                    var r2 = session.ExecuteCommand(cmd);
                    AddLog(cmd);
                    AddLog(r2.Output);
                    AddLog(r2.ErrorOutput);
                }
                catch (Exception e)
                {
                    AddLog(e.ToString(), txt);
                }
            }
        }

        /// <summary>
        /// Called by GetFiles and PutFiles, logs the results of the operation
        /// </summary>
        /// <param name="r"></param>
        void LogTransfer(TransferOperationResult r, TextBox txt)
        {
            AddLog("Transfer: " + (r.IsSuccess ? "Success" : "Fail"), txt);
            if (!r.IsSuccess)
                AddLog("Transfer Error: " + r.Failures, txt);
            foreach (TransferEventArgs t in r.Transfers)
            {
                AddLog(t.FileName + "> " + t.Destination + " : " + (t.Error == null ? "Success" : t.Error.Message), txt);
            }
        }

        bool isRetrictedAppName(string AppName)
        {
            return File.ReadAllLines(System.IO.Path.Combine(Environment.CurrentDirectory, "RestrictedAppName.txt")).Contains(AppName);
        }

        private void Operation_Close(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }
}
